package parser;

import java.util.Arrays;
import java.util.Iterator;

import lexer.Token;
import lexer.TokenType;

/**
 * TokenIterator handles iterating over an Array of tokens. Also performs some
 * matching logic to reduce duplicated boilerplate code for different syntaxes
 */
public class TokenIterator implements Iterator<Token> {
	public Token[] tokens;
	public int pos;

	public TokenIterator(Token[] tokens) {
		if (tokens.length == 0) {
			throw new Error("Tokens passed into TokenIterator has length zero");
		}
		this.tokens = tokens;
		this.pos = 0;
	}

	/**
	 * Throws exception if the next token type is not what is passed in
	 * Increments the internal pointer
	 * 
	 * @param Type
	 *            expected
	 * @return next() if it is what is expected
	 * @throws LogoParseException
	 */
	public Token ExpectedNext(TokenType type) throws LogoParseException {
		Token next = null;
		if (this.hasNext()) {
			next = this.next();
			if (next.getType() == type) {
				return next;
			}
		}
		throw new LogoParseSyntaxException(next, "Expected Token " + type.toString() + ". ");
	}

	/**
	 * Throws exception of the next token type is not what is passed in Does not
	 * move the pointer in the iterator
	 * 
	 * @param The
	 *            Type expected
	 * @throws LogoParseSyntaxException
	 */
	public void ExpectedPeek(TokenType type) throws LogoParseSyntaxException {
		Token next = null;
		if (this.hasNext()) {
			next = this.peek();
			if (next.getType() == type) {
				return;
			}
		}
		throw new LogoParseSyntaxException(next, type.toString());
	}

	/**
	 * Returns true if the next token is what is expected returns false if it
	 * isn't. Less extreme version of ExpectedPeek()
	 * 
	 * @param first
	 * @return true/false
	 */
	boolean matchToken(TokenType first) {
		if (this.hasNext()) {
			if (this.peek().getType() == first) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Matches two tokens in a row
	 * 
	 * @param First token to match
	 * @param Second token to match
	 * @return True if these tokens are the next two in the array. False if they
	 *         are not
	 */
	boolean matchTokenPattern(TokenType first, TokenType second) {
		int pos = this.pos;
		if (this.hasNext()) {
			if (this.next().getType() == first) {
				if (this.hasNext()) {
					if (this.next().getType() == second) {
						this.pos = pos;
						return true;
					}
				}
			}
		}
		this.pos = pos;
		return false;
	}

	/**
	 * Matches a token then an operator.
	 * 
	 * @param Token
	 *            to match
	 * @return true / false
	 */
	boolean matchNextTokenAndOperator(TokenType first) {
		int pos = this.pos;
		if (this.hasNext()) {
			if (this.next().getType() == first) {
				if (matchNextTokenToOperator()) {
					this.pos = pos;
					return true;
				}
			}
		}
		this.pos = pos;
		return false;
	}

	/**
	 * Returns true if the next token is an operator
	 * 
	 * @return boolean
	 */
	boolean matchNextTokenToOperator() {
		// I apologise to the gods of repetitive code. But we need to match an
		// operator somehow
		if (this.matchToken(TokenType.ADD)
				|| this.matchToken(TokenType.SUBTRACT)
				|| this.matchToken(TokenType.MULTIPLY)
				|| this.matchToken(TokenType.DIVIDE)
				|| this.matchToken(TokenType.EQUALS)
				|| this.matchToken(TokenType.NOT_EQUALS)
				|| this.matchToken(TokenType.GREATER_THAN)
				|| this.matchToken(TokenType.GREATER_THAN_OR_EQUAL)
				|| this.matchToken(TokenType.LESS_THAN)
				|| this.matchToken(TokenType.LESS_THAN_OR_EQUAL)) {
			return true;
		}
		return false;
	}

	/**
	 * Creates a new TokenIterator with the supplied bounds
	 * 
	 * @param start. Inclusive
	 * @param stop. <b>Exclusive</b>
	 * @return
	 */
	public TokenIterator split(Token start, Token stop) {
		if (start == stop) {
			throw new Error(
					"Start and end of TokenIterator.split cannot be equal");
		}

		int startIdx = -1;
		int endIdx = -1;
		for (int i = 0; i < tokens.length; i++) {
			if (tokens[i] == start) {
				startIdx = i;
			} else if (tokens[i] == stop) {
				endIdx = i;
			}
		}
		if (startIdx == -1 || endIdx == -1) {
			throw new Error(
					"Unable to find the start and stop tokens in TokenIterator");
		}

		Token[] tokenRange = Arrays.copyOfRange(tokens, startIdx, endIdx);

		return new TokenIterator(tokenRange);
	}

	/**
	 * Creates a new TokenIterator from start to the end of the iterator's array
	 * 
	 * @param start. Inclusive
	 * @return
	 */
	public TokenIterator split(Token start) {
		return this.split(start, this.tokens[this.tokens.length - 1]);
	}

	public int getIndex() {
		return pos;
	}

	@Override
	public boolean hasNext() {
		if (pos < tokens.length) {
			return true;
		}
		return false;
	}

	@Override
	public Token next() {
		if (hasNext()) {
			return tokens[pos++];
		}
		return null;
	}

	public Token peek() {
		if (hasNext()) {
			return tokens[pos];
		}
		return null;
	}

	/**
	 * Never called, but needed for Iterator interface
	 */
	@Override
	public void remove() {
		throw new Error("Cannot remove tokens from TokenIterator");
	}

}
